package com.baidu.iit.pxp.el.juel;

import com.baidu.iit.pxp.el.TypeConverter;
import com.baidu.iit.pxp.el.ValueExpression;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * User: huangweili
 * Date: 14-4-29
 * Time: 下午2:21
 */
public class Bindings implements TypeConverter {
    private static final long serialVersionUID = 1L;

    private static final Method[] NO_FUNCTIONS = new Method[0];
    private static final ValueExpression[] NO_VARIABLES = new ValueExpression[0];


    private static class MethodWrapper implements Serializable {
        private static final long serialVersionUID = 1L;

        private transient Method method;

        private MethodWrapper(Method method) {
            this.method = method;
        }

        private void writeObject(ObjectOutputStream out) throws IOException, ClassNotFoundException {
            out.defaultWriteObject();
            out.writeObject(method.getDeclaringClass());
            out.writeObject(method.getName());
            out.writeObject(method.getParameterTypes());
        }

        private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
            in.defaultReadObject();
            Class<?> type = (Class<?>) in.readObject();
            String name = (String) in.readObject();
            Class<?>[] args = (Class<?>[]) in.readObject();
            try {
                method = type.getDeclaredMethod(name, args);
            } catch (NoSuchMethodException e) {
                throw new IOException(e.getMessage());
            }
        }
    }

    private transient Method[] functions;
    private final ValueExpression[] variables;
    private final TypeConverter converter;


    public Bindings(Method[] functions, ValueExpression[] variables) {
        this(functions, variables, TypeConverter.DEFAULT);
    }


    public Bindings(Method[] functions, ValueExpression[] variables, TypeConverter converter) {
        super();

        this.functions = functions == null || functions.length == 0 ? NO_FUNCTIONS : functions;
        this.variables = variables == null || variables.length == 0 ? NO_VARIABLES : variables;
        this.converter = converter == null ? TypeConverter.DEFAULT : converter;
    }


    public Method getFunction(int index) {
        return functions[index];
    }


    public boolean isFunctionBound(int index) {
        return index >= 0 && index < functions.length;
    }


    public ValueExpression getVariable(int index) {
        return variables[index];
    }


    public boolean isVariableBound(int index) {
        return index >= 0 && index < variables.length && variables[index] != null;
    }


    public <T> T convert(Object value, Class<T> type) {
        return converter.convert(value, type);
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Bindings) {
            Bindings other = (Bindings) obj;
            return Arrays.equals(functions, other.functions)
                    && Arrays.equals(variables, other.variables)
                    && converter.equals(other.converter);
        }
        return false;
    }

    @Override
    public int hashCode() {
        return Arrays.hashCode(functions) ^ Arrays.hashCode(variables) ^ converter.hashCode();
    }

    private void writeObject(ObjectOutputStream out) throws IOException, ClassNotFoundException {
        out.defaultWriteObject();
        MethodWrapper[] wrappers = new MethodWrapper[functions.length];
        for (int i = 0; i < wrappers.length; i++) {
            wrappers[i] = new MethodWrapper(functions[i]);
        }
        out.writeObject(wrappers);
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        MethodWrapper[] wrappers = (MethodWrapper[]) in.readObject();
        if (wrappers.length == 0) {
            functions = NO_FUNCTIONS;
        } else {
            functions = new Method[wrappers.length];
            for (int i = 0; i < functions.length; i++) {
                functions[i] = wrappers[i].method;
            }
        }
    }
}
